This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing a chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

The code should be executed in order.

install.packages("tidyverse")  
Error in install.packages : Updating loaded packages
library(tidyverse)

read in data

clarks <- read.csv("data/Data\ Science\ Assessment.csv")

summarise data

summary(clarks)
       X             cust.id            age         credit.score   email       distance.to.store   online.visits      online.trans    
 Min.   :     1   Min.   :     1   Min.   :13.54   Min.   :504.4   no :70054   Min.   :   0.0306   Min.   :   0.00   Min.   :  0.000  
 1st Qu.: 25001   1st Qu.: 25001   1st Qu.:31.60   1st Qu.:689.9   yes:29946   1st Qu.:   3.2743   1st Qu.:   0.00   1st Qu.:  0.000  
 Median : 50000   Median : 50000   Median :34.97   Median :724.8               Median :   7.3183   Median :   7.00   Median :  2.000  
 Mean   : 50000   Mean   : 50000   Mean   :34.98   Mean   :724.9               Mean   :  14.9791   Mean   :  30.16   Mean   :  9.048  
 3rd Qu.: 75000   3rd Qu.: 75000   3rd Qu.:38.35   3rd Qu.:759.9               3rd Qu.:  16.4615   3rd Qu.:  34.00   3rd Qu.: 10.000  
 Max.   :100000   Max.   :100000   Max.   :56.33   Max.   :941.2               Max.   :1886.2149   Max.   :1046.00   Max.   :298.000  
                                                                                                                                      
  online.spend      store.trans      store.spend       sat.service    sat.selection  
 Min.   :   0.00   Min.   : 0.000   Min.   :   0.00   Min.   :1.0     Min.   :1.00   
 1st Qu.:   0.00   1st Qu.: 0.000   1st Qu.:   0.00   1st Qu.:3.0     1st Qu.:2.00   
 Median :  40.47   Median : 1.000   Median :  30.29   Median :3.0     Median :3.00   
 Mean   : 182.70   Mean   : 1.319   Mean   :  47.17   Mean   :3.1     Mean   :2.81   
 3rd Qu.: 202.27   3rd Qu.: 2.000   3rd Qu.:  65.83   3rd Qu.:4.0     3rd Qu.:3.00   
 Max.   :6781.67   Max.   :26.000   Max.   :1332.51   Max.   :5.0     Max.   :5.00   
                                                      NA's   :34933   NA's   :34933  

Note: Max age is very young - ignoring the fastest growing demographic in UK (the ageing baby boomers).

Very high extremes for many variables, heavilly skewed data

check for missing data - only missing for the satisfaction score

missing.values <- clarks %>%
    gather(key = "key", value = "val") %>%
    mutate(is.missing = is.na(val)) %>%
    group_by(key, is.missing) %>%
    summarise(num.missing = n()) %>%
    filter(is.missing==T) %>%
    select(-is.missing) %>%
    arrange(desc(num.missing)) 
attributes are not identical across measure variables;
they will be dropped
missing.values
missing.values %>%
  ggplot() +
    geom_bar(aes(x=key, y=num.missing), stat = 'identity') +
    labs(x='variable', y="number of missing values", title='Number of missing values') +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Add some new columns

clarks <-  mutate(clarks,
                  total.spend = online.spend + store.spend,
                  total.trans = online.trans + store.trans) 
 
clarks <-  mutate(clarks,
                  prop.spend.online = online.spend /  total.spend,
                  prop.trans.online = online.trans / total.trans) 
clarks <-  mutate(clarks,
                  online.spend.per.trans = online.spend /  online.trans,
                  store.spend.per.trans = store.spend /  store.trans,
                  total.spend.per.trans = total.spend /  total.trans) 

In store spending accounts for 20% of total

total_online = sum(clarks$online.spend)
total_store = sum(clarks$store.spend)
total_store/(total_online+total_store)
[1] 0.2052013

profile customer spending in store or online

clarks$online.profile <- "Mixed"
clarks$online.profile[clarks$online.spend == 0] <- "Store only"
clarks$online.profile[clarks$store.spend == 0] <- "Online only"
clarks$online.profile[clarks$total.spend == 0] <- "No spend"

Characteristics of online profile including “No Spend” - no correlation with age, credit, satisfaction. Distance is interesting - it looks like the No Spenders and the Online only people live further away.

clarks %>% 
  group_by(online.profile) %>%
  summarise(no_rows = length(cust.id))
clarks %>% 
  group_by(online.profile) %>%
  summarise(mean_spend = mean(total.spend))
clarks %>% 
  group_by(online.profile) %>%
  summarise(mean_age = mean(age))
clarks %>% 
  group_by(online.profile) %>%
  summarise(mean_credit = mean(credit.score))
clarks %>% 
  group_by(online.profile) %>%
  summarise(median.distance = median(distance.to.store))
filter(clarks, sat.service != "NA") %>% 
  group_by(online.profile) %>%
  summarise(mean.sat.ser = mean(sat.service))
filter(clarks, sat.selection != "NA") %>% 
  group_by(online.profile) %>%
  summarise(mean.sat.sel = mean(sat.selection))

Data subset of only customers who actually spend something

clarks.spend <- filter(clarks , online.profile != "No spend")

Are there any duplicate customers who may be same person on and offline?

Check duplicates in distance.to.store? - only one and they have different age so all good.

length(unique(clarks$distance.to.store))
[1] 99999
n_occur <- data.frame(table(clarks$distance.to.store))
clarks[clarks$distance.to.store %in% n_occur$Var1[n_occur$Freq > 1],]

First look at correlations: Plot shows positive correlations in red and negative in blue. Bigger circles with stroger colour indicate stronger corelations.

data.numeric <- mutate(clarks, numeric.email = as.numeric(email=="yes"))
rquery.cormat(data.numeric[,c(3,4,6,7,8,9,10,11,12,13,15)])
$r

$p

$sym
                  age credit.score distance.to.store sat.service sat.selection online.spend online.visits online.trans total.trans store.trans
age               1                                                                                                                           
credit.score          1                                                                                                                       
distance.to.store                  1                                                                                                          
sat.service                                          1                                                                                        
sat.selection                                        .           1                                                                            
online.spend                                                                   1                                                              
online.visits                                                                  B            1                                                 
online.trans                                                                   B            B             1                                   
total.trans                                                                    B            B             1            1                      
store.trans                                                                                                                        1          
store.spend                                                                                                                        +          
                  store.spend
age                          
credit.score                 
distance.to.store            
sat.service                  
sat.selection                
online.spend                 
online.visits                
online.trans                 
total.trans                  
store.trans                  
store.spend       1          
attr(,"legend")
[1] 0 ‘ ’ 0.3 ‘.’ 0.6 ‘,’ 0.8 ‘+’ 0.9 ‘*’ 0.95 ‘B’ 1

online spend, visits and transactions are strongly positively correlates store transactions and spend also

satisfaction with service and selection positively correlated age and credit score loosely corelated (positive)

weak negative correlation between distance to store and store spend and treansactions

very weak negative correlation between age and online visit/trans/spend (older people slightly less likely to shop online)

total transaction is far more linked to online spend - probably as store spend in only 20% of total

Are number of online and store transactions positively or negatively correlated? -negative in the extremes but no major correlation

rr

 ggplot(data = clarks, aes(x = store.trans, y = online.trans)) +
          geom_point() 

rr

NA

Plot online and store spends - reveals strange gap in online spending around £28

ggplot(data = clarks.spend, aes(x = store.spend, y = online.spend, colour = online.profile)) +
          geom_point() + guides(colour = guide_legend(override.aes = list(alpha = 1))) 

ggplot(data = clarks.spend, aes(x = store.spend, y = online.spend, colour = online.profile)) +
          geom_point() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
           scale_x_continuous(trans='log10') + scale_y_continuous(trans='log10')

Are transactions and spend correlated? YES

rr

 ggplot(data = clarks, aes(x = total.trans, y = total.spend)) +
          geom_point()

Are prop online and total trans/spend correlated? - Yes, particuarly at the upper end

rr

 
filter(clarks, !is.nan(prop.spend.online)) %>%
ggplot( aes(x = prop.spend.online, y = total.spend)) +
          geom_point()

rr

 
filter(clarks, !is.nan(prop.trans.online)) %>%
ggplot( aes(x = prop.trans.online, y = total.trans)) +
          geom_point()

Online profile

Online only people seem to spend a lot less PER TRANSACTION and within a much smaller range (e.g. for shoes only buy one pair at a time) - incentivise buy one get one 20% off as they add to cart?

clarks.spend %>%
  ggplot( aes(x = online.profile, y = total.spend.per.trans, group = online.profile)) + 
    geom_jitter( alpha=0.05 , colour = "red" )   +
    geom_boxplot( colour = "blue" , alpha=0)  

pivot_longer(clarks.spend, cols = store.spend.per.trans:online.spend.per.trans, names_to = "where", values_to = "ave.spend.per.trans") %>%
ggplot(aes(x = where,y = ave.spend.per.trans), group = where) + 
    geom_jitter( alpha=0.05 , colour = "red" )   +
    geom_boxplot( colour = "blue" , alpha=0)  

Store only people tend not to spend as much OVERALL - can we get them to be returning customers? Offer in bag with purchase? For returning to store or online? Collect email addresses in store? Do we already ask? How high is take up rate?

clarks.spend %>%
  ggplot( aes(x = online.profile, y = total.spend, group = online.profile)) +
    guides(colour = guide_legend(override.aes = list(alpha = 1)))+
   geom_jitter( alpha=0.05 , colour = "red" )   +
    geom_boxplot( colour = "blue" , alpha=0)  

select(clarks.spend, store.spend,online.spend)%>%
pivot_longer( cols = 1:2, names_to = "where", values_to = "spend") %>%
ggplot(aes(x = where,y = spend), group = where) + 
    geom_jitter( alpha=0.05 , colour = "red" )   +
    geom_boxplot( colour = "blue" , alpha=0)  

Email is slightly lower for store only, but actually quite low throughout - perhaps we can encourage email sharing and collect data to see how successful this is. Competition or offer to get people to sign up?

Look at satisfaction

This is not very correlated to anything other than the two metrics being correlated with each other

clarks.spend %>%
  ggplot( aes(x = online.profile, y = sat.selection, group = online.profile)) +
    guides(colour = guide_legend(override.aes = list(alpha = 1)))+
    geom_jitter(alpha = 0.01 , colour = "blue" ) +
    geom_boxplot(alpha = 0)  

How do they have satisfaction scores for no spend people?

clarks[clarks$sat.service != "NA",] %>%
ggplot(aes(online.profile)) + 
  geom_bar(aes(fill = as.factor(sat.service)), position = "fill")

Age

rr ggplot(data = clarks.spend, aes(x = age, y = total.spend, colour = online.profile)) + geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1))) + scale_y_continuous(trans=‘log2’)

rr

 ggplot(data = clarks.spend, aes(x = age, y = total.spend.per.trans, colour = online.profile)) +
          geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1))) 

It seems there is little relationship between age and TOTAL SPEND, but old and young spend less PER TRANSACTION. Perhaps those in 30s buy for their whole family at once but can’t budget more overall. Or they have less time to shop so buy all at once.

rr

filter(clarks.spend , online.profile != \Store only\) %>%
 ggplot(aes(x = age, y = total.spend.per.trans, colour = online.profile)) +
          geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1))) 

Credit score

rr

 ggplot(data = clarks.spend, aes(x = credit.score, y = total.spend, colour = online.profile)) +
          geom_point(alpha = 0.1)+ guides(colour = guide_legend(override.aes = list(alpha = 1))) +
 scale_y_continuous(trans='log2')

rr

 ggplot(data = clarks.spend, aes(x = credit.score, y = total.spend.per.trans, colour = online.profile)) +
          geom_point(alpha = 0.1)+ guides(colour = guide_legend(override.aes = list(alpha = 1))) +
 scale_y_continuous(trans='log2')

distance to store

rr

filter(clarks.spend, distance.to.store < 0.99* max(distance.to.store)) %>%
ggplot( aes(x = distance.to.store, y = total.spend, colour = online.profile)) +
          geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
         scale_x_continuous(trans='log10') + scale_y_continuous(trans='log2')

Can see what you would expect here - those closer to store more likely to spend in store. those far away more likely to shop online only.

Below are lots more pots which may be of interest in future with different data but did not yield much insight

rr

 ggplot(data = clarks, aes(x = online.visits, y = total.spend,  colour = online.profile)) +
          geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

rr

 ggplot(data = clarks, aes(x = online.visits, y = online.spend, colour = online.profile)) +
          geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

rr

 ggplot(data = clarks, aes(x = online.visits, y = store.spend, colour = online.profile)) +
          geom_point(alpha = 0.1) + guides(colour = guide_legend(override.aes = list(alpha = 1)))

 ggplot(data = clarks, aes(x = store.trans, y = store.spend, colour = prop.spend.online)) +
          geom_point(alpha = 0.1)

 ggplot(data = clarks, aes(x = store.trans, y = online.spend, colour = prop.spend.online)) +
          geom_point(alpha = 0.1)

 ggplot(data = clarks, aes(x = store.trans, y = total.spend, colour = prop.spend.online)) +
          geom_point(alpha = 0.1)

Are older people more satisfied?

clarks %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c("<20","20s","30s","40s","50s"))) %>%
  group_by(age.group) %>%
  filter(sat.service != "NA") %>%
    summarize(mean.satisfaction = mean(sat.service))

Are older people spending less per transaction? I did look this way, but actually just fewer data points in these categories.

rr

clarks %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c(\<20\,\20s\,\30s\,\40s\,\50s\))) %>%
  ggplot( aes(x = age.group, y = total.spend.per.trans, group = age.group, colour = online.profile)) +
    guides(colour = guide_legend(override.aes = list(alpha = 1)))+
    geom_jitter(alpha = 0.1 ) +
    geom_boxplot(alpha = 0)  

Are older people spending more money?

clarks %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c("<20","20s","30s","40s","50s"))) %>%
  ggplot( aes(x = age.group, y = total.spend, group = age.group, colour = online.profile)) +
    guides(colour = guide_legend(override.aes = list(alpha = 1)))+
    geom_jitter(alpha = 0.1 ) +
    geom_boxplot(alpha = 0)  

Are older people spending more frequently?

clarks.spend %>%
mutate(age.group=cut(age, breaks=c(0, 20, 30, 40, 50, 100), labels=c("<20","20s","30s","40s","50s"))) %>%
  ggplot( aes(x = age.group, y = total.trans, group = age.group, colour = online.profile)) +
    guides(colour = guide_legend(override.aes = list(alpha = 1)))+
    geom_jitter(alpha = 0.1 ) +
    geom_boxplot(alpha = 0)  

# Satisfaction as a input?

clarks %>% 
  group_by(sat.service) %>%
  summarise(no_rows = length(cust.id))
clarks %>% 
  group_by(sat.service) %>%
  summarise(mean_spend = mean(total.spend))
clarks %>% 
  group_by(sat.service) %>%
  summarise(mean_age = mean(age))
clarks %>% 
  group_by(sat.service) %>%
  summarise(mean_credit = mean(credit.score))
clarks %>% 
  group_by(sat.service) %>%
  summarise(median.distance = median(distance.to.store))
filter(clarks, sat.selection != "NA") %>% 
  group_by(sat.service) %>%
  summarise(mean.sat.sel = mean(sat.selection))

There doesn’t appear to be any correlation between satisfaction or service and any other variables, except for satisfaction of selection which you would expect high scorers would score high on both

Are more satisfied people making more purchases?

filter(clarks, sat.service != "NA") %>%
ggplot( aes(x = sat.service, y = total.trans, group = sat.service, colour = online.profile)) +
    guides(colour = guide_legend(override.aes = list(alpha = 1)))+
    geom_jitter(alpha = 0.1 ) +
    geom_boxplot(alpha = 0)  

Are more satisfied people spending more?

rr


filter(clarks, sat.service != \NA\) %>%
ggplot( aes(x = sat.service, y = total.spend, group = sat.service, color = prop.spend.online)) +
    geom_jitter(alpha = 0.1 ) +
    geom_boxplot(alpha = 0)  

rr

filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.selection, y = total.spend, group = sat.selection, color = prop.spend.online)) +
    geom_jitter(alpha = 0.1 ) +
    geom_boxplot(alpha = 0)  

rr

filter(clarks, sat.service != \NA\) %>%
filter( !is.nan(prop.spend.online)) %>%
ggplot( aes(x = prop.spend.online, y = sat.service)) +
    geom_point(alpha = 0.1 ) 

rr


filter(clarks, sat.service != \NA\) %>%
ggplot( aes(x = sat.service, y =  prop.spend.online, group = sat.service)) +
    geom_jitter(alpha = 0.05 ) +
    geom_violin(alpha = 0, colour = \red\)  

rr


filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.selection, y =  prop.spend.online, group = sat.selection)) +
    geom_jitter(alpha = 0.05 ) +
    geom_violin(alpha = 0, colour = \red\)  

rr clarks%>% group_by(sat.selection)%>% summarize(mean.spend = mean(total.spend))%>% ggplot( aes(x=sat.selection, y=mean.spend)) + geom_col()

rr

clarks%>%
group_by(sat.service)%>%
summarize(mean.spend = mean(total.spend))%>%
  ggplot( aes(x=sat.service, y=mean.spend)) + 
  geom_col()

rr


filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.selection)) +
    geom_bar( ) 
  

rr

filter(clarks, sat.selection != \NA\) %>%
ggplot( aes(x = sat.service)) +
    geom_bar( ) 
  

rr

clarks%>%
mutate(no.online.visits = online.visits==0) %>% 
ggplot( aes(x = no.online.visits, y = total.spend, group = no.online.visits, colour = sat.service)) +
    geom_jitter(alpha = 0.05 ) +
    geom_violin(alpha = 0, colour = \red\)  

rr

clarks.spend%>%
filter(online.profile != \None\) %>%
ggplot( aes(x = online.profile, y = total.spend, group = online.profile, colour = sat.service)) +
    geom_jitter(alpha = 0.05 ) +
    geom_violin(alpha = 0, colour = \red\)  

rr

clarks.spend%>%
filter(online.profile != \None\) %>%
ggplot( aes(x = online.profile, y = distance.to.store, group = online.profile, colour = sat.service)) +
    geom_jitter(alpha = 0.05 ) +
    geom_violin(alpha = 0, colour = \red\)  

rr

clarks%>%
filter(store.spend>0)%>%
ggplot( aes(x = distance.to.store, y = store.spend, colour = sat.service)) +
geom_point(alpha=0.1)

rr

clarks%>%
mutate(store = store.spend > 0)%>%
group_by(store) %>%
summarize(mean_spend = mean(online.spend))

rr

clarks%>%
mutate(store = store.spend > 0)%>%
ggplot(aes(x=store, y=total.spend , group=store))+
  geom_violin()
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayAtIENsYXJrcyBpbnRlcnZpZXcgYXNzaWdubWVudCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIGEgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouIAoKQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IgYnkgcHJlc3NpbmcgKkN0cmwrQWx0K0kqLgoKV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDdHJsK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuCgpUaGUgcHJldmlldyBzaG93cyB5b3UgYSByZW5kZXJlZCBIVE1MIGNvcHkgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBlZGl0b3IuIENvbnNlcXVlbnRseSwgdW5saWtlICpLbml0KiwgKlByZXZpZXcqIGRvZXMgbm90IHJ1biBhbnkgUiBjb2RlIGNodW5rcy4gSW5zdGVhZCwgdGhlIG91dHB1dCBvZiB0aGUgY2h1bmsgd2hlbiBpdCB3YXMgbGFzdCBydW4gaW4gdGhlIGVkaXRvciBpcyBkaXNwbGF5ZWQuCgpUaGUgY29kZSBzaG91bGQgYmUgZXhlY3V0ZWQgaW4gb3JkZXIuCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikgIAppbnN0YWxsLnBhY2thZ2VzKCJjb3JycGxvdCIpCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KCJjb3JycGxvdCIpCmBgYAoKcmVhZCBpbiBkYXRhCmBgYHtyfQpjbGFya3MgPC0gcmVhZC5jc3YoImRhdGEvRGF0YVwgU2NpZW5jZVwgQXNzZXNzbWVudC5jc3YiKQpgYGAKCnN1bW1hcmlzZSBkYXRhCmBgYHtyfQpzdW1tYXJ5KGNsYXJrcykKYGBgCgojIE5vdGU6IE1heCBhZ2UgaXMgdmVyeSB5b3VuZyAtIGlnbm9yaW5nIHRoZSBmYXN0ZXN0IGdyb3dpbmcgZGVtb2dyYXBoaWMgaW4gVUsgKHRoZSBhZ2VpbmcgYmFieSBib29tZXJzKS4KIyBWZXJ5IGhpZ2ggZXh0cmVtZXMgZm9yIG1hbnkgdmFyaWFibGVzLCBoZWF2aWxseSBza2V3ZWQgZGF0YQoKCgpjaGVjayBmb3IgbWlzc2luZyBkYXRhIC0gb25seSBtaXNzaW5nIGZvciB0aGUgc2F0aXNmYWN0aW9uIHNjb3JlCmBgYHtyfQptaXNzaW5nLnZhbHVlcyA8LSBjbGFya3MgJT4lCiAgICBnYXRoZXIoa2V5ID0gImtleSIsIHZhbHVlID0gInZhbCIpICU+JQogICAgbXV0YXRlKGlzLm1pc3NpbmcgPSBpcy5uYSh2YWwpKSAlPiUKICAgIGdyb3VwX2J5KGtleSwgaXMubWlzc2luZykgJT4lCiAgICBzdW1tYXJpc2UobnVtLm1pc3NpbmcgPSBuKCkpICU+JQogICAgZmlsdGVyKGlzLm1pc3Npbmc9PVQpICU+JQogICAgc2VsZWN0KC1pcy5taXNzaW5nKSAlPiUKICAgIGFycmFuZ2UoZGVzYyhudW0ubWlzc2luZykpIAoKbWlzc2luZy52YWx1ZXMKYGBgCmBgYHtyfQptaXNzaW5nLnZhbHVlcyAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX2JhcihhZXMoeD1rZXksIHk9bnVtLm1pc3NpbmcpLCBzdGF0ID0gJ2lkZW50aXR5JykgKwogICAgbGFicyh4PSd2YXJpYWJsZScsIHk9Im51bWJlciBvZiBtaXNzaW5nIHZhbHVlcyIsIHRpdGxlPSdOdW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMnKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCgoKCiMgQWRkIHNvbWUgbmV3IGNvbHVtbnMKYGBge3J9CmNsYXJrcyA8LSAgbXV0YXRlKGNsYXJrcywKICAgICAgICAgICAgICAgICAgdG90YWwuc3BlbmQgPSBvbmxpbmUuc3BlbmQgKyBzdG9yZS5zcGVuZCwKICAgICAgICAgICAgICAgICAgdG90YWwudHJhbnMgPSBvbmxpbmUudHJhbnMgKyBzdG9yZS50cmFucykgCiAKCmNsYXJrcyA8LSAgbXV0YXRlKGNsYXJrcywKICAgICAgICAgICAgICAgICAgcHJvcC5zcGVuZC5vbmxpbmUgPSBvbmxpbmUuc3BlbmQgLyAgdG90YWwuc3BlbmQsCiAgICAgICAgICAgICAgICAgIHByb3AudHJhbnMub25saW5lID0gb25saW5lLnRyYW5zIC8gdG90YWwudHJhbnMpIAoKCmNsYXJrcyA8LSAgbXV0YXRlKGNsYXJrcywKICAgICAgICAgICAgICAgICAgb25saW5lLnNwZW5kLnBlci50cmFucyA9IG9ubGluZS5zcGVuZCAvICBvbmxpbmUudHJhbnMsCiAgICAgICAgICAgICAgICAgIHN0b3JlLnNwZW5kLnBlci50cmFucyA9IHN0b3JlLnNwZW5kIC8gIHN0b3JlLnRyYW5zLAogICAgICAgICAgICAgICAgICB0b3RhbC5zcGVuZC5wZXIudHJhbnMgPSB0b3RhbC5zcGVuZCAvICB0b3RhbC50cmFucykgCmBgYAoKCgojIEluIHN0b3JlIHNwZW5kaW5nIGFjY291bnRzIGZvciAyMCUgb2YgdG90YWwKYGBge3J9CnRvdGFsX29ubGluZSA9IHN1bShjbGFya3Mkb25saW5lLnNwZW5kKQp0b3RhbF9zdG9yZSA9IHN1bShjbGFya3Mkc3RvcmUuc3BlbmQpCgp0b3RhbF9zdG9yZS8odG90YWxfb25saW5lK3RvdGFsX3N0b3JlKQpgYGAKCgojIHByb2ZpbGUgY3VzdG9tZXIgc3BlbmRpbmcgaW4gc3RvcmUgb3Igb25saW5lCmBgYHtyfQpjbGFya3Mkb25saW5lLnByb2ZpbGUgPC0gIk1peGVkIgpjbGFya3Mkb25saW5lLnByb2ZpbGVbY2xhcmtzJG9ubGluZS5zcGVuZCA9PSAwXSA8LSAiU3RvcmUgb25seSIKY2xhcmtzJG9ubGluZS5wcm9maWxlW2NsYXJrcyRzdG9yZS5zcGVuZCA9PSAwXSA8LSAiT25saW5lIG9ubHkiCmNsYXJrcyRvbmxpbmUucHJvZmlsZVtjbGFya3MkdG90YWwuc3BlbmQgPT0gMF0gPC0gIk5vIHNwZW5kIgpgYGAKCgoKI0NoYXJhY3RlcmlzdGljcyBvZiBvbmxpbmUgcHJvZmlsZSBpbmNsdWRpbmcgIk5vIFNwZW5kIiAgLSBubyBjb3JyZWxhdGlvbiB3aXRoIGFnZSwgY3JlZGl0LCBzYXRpc2ZhY3Rpb24uIERpc3RhbmNlIGlzIGludGVyZXN0aW5nIC0gaXQgbG9va3MgbGlrZSB0aGUgTm8gU3BlbmRlcnMgYW5kIHRoZSBPbmxpbmUgb25seSBwZW9wbGUgbGl2ZSBmdXJ0aGVyIGF3YXkuCmBgYHtyfQpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2Uobm9fcm93cyA9IGxlbmd0aChjdXN0LmlkKSkKCmNsYXJrcyAlPiUgCiAgZ3JvdXBfYnkob25saW5lLnByb2ZpbGUpICU+JQogIHN1bW1hcmlzZShtZWFuX3NwZW5kID0gbWVhbih0b3RhbC5zcGVuZCkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9hZ2UgPSBtZWFuKGFnZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9jcmVkaXQgPSBtZWFuKGNyZWRpdC5zY29yZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KG9ubGluZS5wcm9maWxlKSAlPiUKICBzdW1tYXJpc2UobWVkaWFuLmRpc3RhbmNlID0gbWVkaWFuKGRpc3RhbmNlLnRvLnN0b3JlKSkKCmZpbHRlcihjbGFya3MsIHNhdC5zZXJ2aWNlICE9ICJOQSIpICU+JSAKICBncm91cF9ieShvbmxpbmUucHJvZmlsZSkgJT4lCiAgc3VtbWFyaXNlKG1lYW4uc2F0LnNlciA9IG1lYW4oc2F0LnNlcnZpY2UpKQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlbGVjdGlvbiAhPSAiTkEiKSAlPiUgCiAgZ3JvdXBfYnkob25saW5lLnByb2ZpbGUpICU+JQogIHN1bW1hcmlzZShtZWFuLnNhdC5zZWwgPSBtZWFuKHNhdC5zZWxlY3Rpb24pKQoKYGBgCgoKIyBEYXRhIHN1YnNldCBvZiBvbmx5IGN1c3RvbWVycyB3aG8gYWN0dWFsbHkgc3BlbmQgc29tZXRoaW5nCmBgYHtyfQpjbGFya3Muc3BlbmQgPC0gZmlsdGVyKGNsYXJrcyAsIG9ubGluZS5wcm9maWxlICE9ICJObyBzcGVuZCIpCmBgYAoKCgojQXJlIHRoZXJlIGFueSBkdXBsaWNhdGUgY3VzdG9tZXJzIHdobyBtYXkgYmUgc2FtZSBwZXJzb24gb24gYW5kIG9mZmxpbmU/CiNDaGVjayBkdXBsaWNhdGVzIGluIGRpc3RhbmNlLnRvLnN0b3JlPyAtIG9ubHkgb25lIGFuZCB0aGV5IGhhdmUgZGlmZmVyZW50IGFnZSBzbyBhbGwgZ29vZC4KYGBge3J9Cmxlbmd0aCh1bmlxdWUoY2xhcmtzJGRpc3RhbmNlLnRvLnN0b3JlKSkKCm5fb2NjdXIgPC0gZGF0YS5mcmFtZSh0YWJsZShjbGFya3MkZGlzdGFuY2UudG8uc3RvcmUpKQpjbGFya3NbY2xhcmtzJGRpc3RhbmNlLnRvLnN0b3JlICVpbiUgbl9vY2N1ciRWYXIxW25fb2NjdXIkRnJlcSA+IDFdLF0KCmBgYAoKCkZpcnN0IGxvb2sgYXQgY29ycmVsYXRpb25zOgpQbG90IHNob3dzIHBvc2l0aXZlIGNvcnJlbGF0aW9ucyBpbiByZWQgYW5kIG5lZ2F0aXZlIGluIGJsdWUuIEJpZ2dlciBjaXJjbGVzIHdpdGggc3Ryb2dlciBjb2xvdXIgaW5kaWNhdGUgc3Ryb25nZXIgY29yZWxhdGlvbnMuCmBgYHtyfQpkYXRhLm51bWVyaWMgPC0gbXV0YXRlKGNsYXJrcywgbnVtZXJpYy5lbWFpbCA9IGFzLm51bWVyaWMoZW1haWw9PSJ5ZXMiKSkKcnF1ZXJ5LmNvcm1hdChkYXRhLm51bWVyaWNbLGMoMyw0LDYsNyw4LDksMTAsMTEsMTIsMTMsMTUpXSkKYGBgCgpvbmxpbmUgc3BlbmQsIHZpc2l0cyBhbmQgdHJhbnNhY3Rpb25zIGFyZSBzdHJvbmdseSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZXMKc3RvcmUgdHJhbnNhY3Rpb25zIGFuZCBzcGVuZCBhbHNvCgpzYXRpc2ZhY3Rpb24gd2l0aCBzZXJ2aWNlIGFuZCBzZWxlY3Rpb24gcG9zaXRpdmVseSBjb3JyZWxhdGVkCmFnZSBhbmQgY3JlZGl0IHNjb3JlIGxvb3NlbHkgY29yZWxhdGVkIChwb3NpdGl2ZSkKCndlYWsgbmVnYXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBkaXN0YW5jZSB0byBzdG9yZSBhbmQgc3RvcmUgc3BlbmQgYW5kIHRyZWFuc2FjdGlvbnMKCnZlcnkgd2VhayBuZWdhdGl2ZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGFnZSBhbmQgb25saW5lIHZpc2l0L3RyYW5zL3NwZW5kIChvbGRlciBwZW9wbGUgc2xpZ2h0bHkgbGVzcyBsaWtlbHkgdG8gc2hvcCBvbmxpbmUpCgp0b3RhbCB0cmFuc2FjdGlvbiBpcyBmYXIgbW9yZSBsaW5rZWQgdG8gb25saW5lIHNwZW5kIC0gcHJvYmFibHkgYXMgc3RvcmUgc3BlbmQgaW4gb25seSAyMCUgb2YgdG90YWwKCgoKCiMgQXJlIG51bWJlciBvZiBvbmxpbmUgYW5kIHN0b3JlIHRyYW5zYWN0aW9ucyBwb3NpdGl2ZWx5IG9yIG5lZ2F0aXZlbHkgY29ycmVsYXRlZD8gLW5lZ2F0aXZlIGluIHRoZSBleHRyZW1lcyBidXQgbm8gbWFqb3IgY29ycmVsYXRpb24KYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcywgYWVzKHggPSBzdG9yZS50cmFucywgeSA9IG9ubGluZS50cmFucykpICsKICAgICAgICAgIGdlb21fcG9pbnQoKSAKICAgICAgICAKICAgIGBgYAoKCgpQbG90IG9ubGluZSBhbmQgc3RvcmUgc3BlbmRzIC0gcmV2ZWFscyBzdHJhbmdlIGdhcCBpbiBvbmxpbmUgc3BlbmRpbmcgYXJvdW5kIMKjMjgKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBzdG9yZS5zcGVuZCwgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KCkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpIApgYGAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBzdG9yZS5zcGVuZCwgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KCkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpICsKICAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykgKyBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykKYGBgCgoKQXJlIHRyYW5zYWN0aW9ucyBhbmQgc3BlbmQgY29ycmVsYXRlZD8gIFlFUwpgYGB7cn0KIGdncGxvdChkYXRhID0gY2xhcmtzLCBhZXMoeCA9IHRvdGFsLnRyYW5zLCB5ID0gdG90YWwuc3BlbmQpKSArCiAgICAgICAgICBnZW9tX3BvaW50KCkKYGBgCgoKQXJlIHByb3Agb25saW5lIGFuZCB0b3RhbCB0cmFucy9zcGVuZCBjb3JyZWxhdGVkPyAgLSBZZXMsIHBhcnRpY3Vhcmx5IGF0IHRoZSB1cHBlciBlbmQKYGBge3J9CiAKZmlsdGVyKGNsYXJrcywgIWlzLm5hbihwcm9wLnNwZW5kLm9ubGluZSkpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gcHJvcC5zcGVuZC5vbmxpbmUsIHkgPSB0b3RhbC5zcGVuZCkpICsKICAgICAgICAgIGdlb21fcG9pbnQoKQpgYGAKYGBge3J9CiAKZmlsdGVyKGNsYXJrcywgIWlzLm5hbihwcm9wLnRyYW5zLm9ubGluZSkpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gcHJvcC50cmFucy5vbmxpbmUsIHkgPSB0b3RhbC50cmFucykpICsKICAgICAgICAgIGdlb21fcG9pbnQoKQpgYGAKCgoKCiMgT25saW5lIHByb2ZpbGUKT25saW5lIG9ubHkgcGVvcGxlIHNlZW0gdG8gc3BlbmQgYSBsb3QgbGVzcyBQRVIgVFJBTlNBQ1RJT04gYW5kIHdpdGhpbiBhIG11Y2ggc21hbGxlciByYW5nZSAoZS5nLiBmb3Igc2hvZXMgb25seSBidXkgb25lIHBhaXIgYXQgYSB0aW1lKSAtIGluY2VudGl2aXNlIGJ1eSBvbmUgZ2V0IG9uZSAyMCUgb2ZmIGFzIHRoZXkgYWRkIHRvIGNhcnQ/CgpgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQogIGdncGxvdCggYWVzKHggPSBvbmxpbmUucHJvZmlsZSwgeSA9IHRvdGFsLnNwZW5kLnBlci50cmFucywgZ3JvdXAgPSBvbmxpbmUucHJvZmlsZSkpICsgCiAgICBnZW9tX2ppdHRlciggYWxwaGE9MC4wNSAsIGNvbG91ciA9ICJyZWQiICkgICArCiAgICBnZW9tX2JveHBsb3QoIGNvbG91ciA9ICJibHVlIiAsIGFscGhhPTApICAKYGBgCgoKYGBge3J9CnBpdm90X2xvbmdlcihjbGFya3Muc3BlbmQsIGNvbHMgPSBzdG9yZS5zcGVuZC5wZXIudHJhbnM6b25saW5lLnNwZW5kLnBlci50cmFucywgbmFtZXNfdG8gPSAid2hlcmUiLCB2YWx1ZXNfdG8gPSAiYXZlLnNwZW5kLnBlci50cmFucyIpICU+JQpnZ3Bsb3QoYWVzKHggPSB3aGVyZSx5ID0gYXZlLnNwZW5kLnBlci50cmFucyksIGdyb3VwID0gd2hlcmUpICsgCiAgICBnZW9tX2ppdHRlciggYWxwaGE9MC4wNSAsIGNvbG91ciA9ICJyZWQiICkgICArCiAgICBnZW9tX2JveHBsb3QoIGNvbG91ciA9ICJibHVlIiAsIGFscGhhPTApICAKYGBgCgoKClN0b3JlIG9ubHkgcGVvcGxlIHRlbmQgbm90IHRvIHNwZW5kIGFzIG11Y2ggT1ZFUkFMTCAtIGNhbiB3ZSBnZXQgdGhlbSB0byBiZSByZXR1cm5pbmcgY3VzdG9tZXJzPyBPZmZlciBpbiBiYWcgd2l0aCBwdXJjaGFzZT8gRm9yIHJldHVybmluZyB0byBzdG9yZSBvciBvbmxpbmU/IENvbGxlY3QgZW1haWwgYWRkcmVzc2VzIGluIHN0b3JlPyBEbyB3ZSBhbHJlYWR5IGFzaz8gSG93IGhpZ2ggaXMgdGFrZSB1cCByYXRlPwoKYGBge3J9CmNsYXJrcy5zcGVuZCAlPiUKICBnZ3Bsb3QoIGFlcyh4ID0gb25saW5lLnByb2ZpbGUsIHkgPSB0b3RhbC5zcGVuZCwgZ3JvdXAgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkrCiAgIGdlb21faml0dGVyKCBhbHBoYT0wLjA1ICwgY29sb3VyID0gInJlZCIgKSAgICsKICAgIGdlb21fYm94cGxvdCggY29sb3VyID0gImJsdWUiICwgYWxwaGE9MCkgIApgYGAKCmBgYHtyfQoKc2VsZWN0KGNsYXJrcy5zcGVuZCwgc3RvcmUuc3BlbmQsb25saW5lLnNwZW5kKSU+JQpwaXZvdF9sb25nZXIoIGNvbHMgPSAxOjIsIG5hbWVzX3RvID0gIndoZXJlIiwgdmFsdWVzX3RvID0gInNwZW5kIikgJT4lCmdncGxvdChhZXMoeCA9IHdoZXJlLHkgPSBzcGVuZCksIGdyb3VwID0gd2hlcmUpICsgCiAgICBnZW9tX2ppdHRlciggYWxwaGE9MC4wNSAsIGNvbG91ciA9ICJyZWQiICkgICArCiAgICBnZW9tX2JveHBsb3QoIGNvbG91ciA9ICJibHVlIiAsIGFscGhhPTApICAKYGBgCgoKCkVtYWlsIGlzIHNsaWdodGx5IGxvd2VyIGZvciBzdG9yZSBvbmx5LCBidXQgYWN0dWFsbHkgcXVpdGUgbG93IHRocm91Z2hvdXQgLSBwZXJoYXBzIHdlIGNhbiBlbmNvdXJhZ2UgZW1haWwgc2hhcmluZyBhbmQgY29sbGVjdCBkYXRhIHRvIHNlZSBob3cgc3VjY2Vzc2Z1bCB0aGlzIGlzLiBDb21wZXRpdGlvbiBvciBvZmZlciB0byBnZXQgcGVvcGxlIHRvIHNpZ24gdXA/CgpgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQpnZ3Bsb3QoYWVzKG9ubGluZS5wcm9maWxlKSkgKyAKICBnZW9tX2JhcihhZXMoZmlsbCA9IGFzLmZhY3RvcihlbWFpbCkpLCBwb3NpdGlvbiA9ICJmaWxsIikKYGBgCgoKCgoKIyBMb29rIGF0IHNhdGlzZmFjdGlvbiAKVGhpcyBpcyBub3QgdmVyeSBjb3JyZWxhdGVkIHRvIGFueXRoaW5nIG90aGVyIHRoYW4gdGhlIHR3byBtZXRyaWNzIGJlaW5nIGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyCgpgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQogIGdncGxvdCggYWVzKHggPSBvbmxpbmUucHJvZmlsZSwgeSA9IHNhdC5zZWxlY3Rpb24sIGdyb3VwID0gb25saW5lLnByb2ZpbGUpKSArCiAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjAxICwgY29sb3VyID0gImJsdWUiICkgKwogICAgZ2VvbV9ib3hwbG90KGFscGhhID0gMCkgIApgYGAKCkhvdyBkbyB0aGV5IGhhdmUgc2F0aXNmYWN0aW9uIHNjb3JlcyBmb3Igbm8gc3BlbmQgcGVvcGxlPwpgYGB7cn0KY2xhcmtzW2NsYXJrcyRzYXQuc2VydmljZSAhPSAiTkEiLF0gJT4lCmdncGxvdChhZXMob25saW5lLnByb2ZpbGUpKSArIAogIGdlb21fYmFyKGFlcyhmaWxsID0gYXMuZmFjdG9yKHNhdC5zZXJ2aWNlKSksIHBvc2l0aW9uID0gImZpbGwiKQpgYGAKCiMgQWdlCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3Muc3BlbmQsIGFlcyh4ID0gYWdlLCB5ID0gdG90YWwuc3BlbmQsIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpICsKICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzInKSAKYGBgCgoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBhZ2UsIHkgPSB0b3RhbC5zcGVuZC5wZXIudHJhbnMsIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpIApgYGAKSXQgc2VlbXMgdGhlcmUgaXMgbGl0dGxlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGFnZSBhbmQgVE9UQUwgU1BFTkQsIGJ1dCBvbGQgYW5kIHlvdW5nIHNwZW5kIGxlc3MgUEVSIFRSQU5TQUNUSU9OLiBQZXJoYXBzIHRob3NlIGluIDMwcyBidXkgZm9yIHRoZWlyIHdob2xlIGZhbWlseSBhdCBvbmNlIGJ1dCBjYW4ndCBidWRnZXQgbW9yZSBvdmVyYWxsLiBPciB0aGV5IGhhdmUgbGVzcyB0aW1lIHRvIHNob3Agc28gYnV5IGFsbCBhdCBvbmNlLgoKCgoKYGBge3J9CgpmaWx0ZXIoY2xhcmtzLnNwZW5kICwgb25saW5lLnByb2ZpbGUgIT0gIlN0b3JlIG9ubHkiKSAlPiUKIGdncGxvdChhZXMoeCA9IGFnZSwgeSA9IHRvdGFsLnNwZW5kLnBlci50cmFucywgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkgCmBgYAoKCgoKCgojIENyZWRpdCBzY29yZQoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcy5zcGVuZCwgYWVzKHggPSBjcmVkaXQuc2NvcmUsIHkgPSB0b3RhbC5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSsgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGFscGhhID0gMSkpKSArCiBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzInKQpgYGAKCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3Muc3BlbmQsIGFlcyh4ID0gY3JlZGl0LnNjb3JlLCB5ID0gdG90YWwuc3BlbmQucGVyLnRyYW5zLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpICsKIHNjYWxlX3lfY29udGludW91cyh0cmFucz0nbG9nMicpCmBgYAoKIyBkaXN0YW5jZSB0byBzdG9yZQoKYGBge3J9CmZpbHRlcihjbGFya3Muc3BlbmQsIGRpc3RhbmNlLnRvLnN0b3JlIDwgMC45OSogbWF4KGRpc3RhbmNlLnRvLnN0b3JlKSkgJT4lCgpnZ3Bsb3QoIGFlcyh4ID0gZGlzdGFuY2UudG8uc3RvcmUsIHkgPSB0b3RhbC5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkgKwogICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXModHJhbnM9J2xvZzEwJykgKyBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9J2xvZzInKQpgYGAKCkNhbiBzZWUgd2hhdCB5b3Ugd291bGQgZXhwZWN0IGhlcmUgLSB0aG9zZSBjbG9zZXIgdG8gc3RvcmUgbW9yZSBsaWtlbHkgdG8gc3BlbmQgaW4gc3RvcmUuIHRob3NlIGZhciBhd2F5IG1vcmUgbGlrZWx5IHRvIHNob3Agb25saW5lIG9ubHkuCgoKCgojIEJlbG93IGFyZSBsb3RzIG1vcmUgcG90cyB3aGljaCBtYXkgYmUgb2YgaW50ZXJlc3QgaW4gZnV0dXJlIHdpdGggZGlmZmVyZW50IGRhdGEgYnV0IGRpZCBub3QgeWllbGQgbXVjaCBpbnNpZ2h0CgpgYGB7cn0KIGdncGxvdChkYXRhID0gY2xhcmtzLCBhZXMoeCA9IG9ubGluZS52aXNpdHMsIHkgPSB0b3RhbC5zcGVuZCwgIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpCmBgYAoKCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3MsIGFlcyh4ID0gb25saW5lLnZpc2l0cywgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkKYGBgCmBgYHtyfQogZ2dwbG90KGRhdGEgPSBjbGFya3MsIGFlcyh4ID0gb25saW5lLnZpc2l0cywgeSA9IHN0b3JlLnNwZW5kLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpICsgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGFscGhhID0gMSkpKQpgYGAKCgoKCgoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcywgYWVzKHggPSBzdG9yZS50cmFucywgeSA9IHN0b3JlLnNwZW5kLCBjb2xvdXIgPSBwcm9wLnNwZW5kLm9ubGluZSkpICsKICAgICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpCmBgYAoKYGBge3J9CiBnZ3Bsb3QoZGF0YSA9IGNsYXJrcywgYWVzKHggPSBzdG9yZS50cmFucywgeSA9IG9ubGluZS5zcGVuZCwgY29sb3VyID0gcHJvcC5zcGVuZC5vbmxpbmUpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKQpgYGAKCgpgYGB7cn0KIGdncGxvdChkYXRhID0gY2xhcmtzLCBhZXMoeCA9IHN0b3JlLnRyYW5zLCB5ID0gdG90YWwuc3BlbmQsIGNvbG91ciA9IHByb3Auc3BlbmQub25saW5lKSkgKwogICAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkKYGBgCgoKCgoKIEFyZSBvbGRlciBwZW9wbGUgbW9yZSBzYXRpc2ZpZWQ/IAogCmBgYHtyfQpjbGFya3MgJT4lCm11dGF0ZShhZ2UuZ3JvdXA9Y3V0KGFnZSwgYnJlYWtzPWMoMCwgMjAsIDMwLCA0MCwgNTAsIDEwMCksIGxhYmVscz1jKCI8MjAiLCIyMHMiLCIzMHMiLCI0MHMiLCI1MHMiKSkpICU+JQogIGdyb3VwX2J5KGFnZS5ncm91cCkgJT4lCiAgZmlsdGVyKHNhdC5zZXJ2aWNlICE9ICJOQSIpICU+JQogICAgc3VtbWFyaXplKG1lYW4uc2F0aXNmYWN0aW9uID0gbWVhbihzYXQuc2VydmljZSkpCgpgYGAKIAoKIAogIEFyZSBvbGRlciBwZW9wbGUgc3BlbmRpbmcgbGVzcyBwZXIgdHJhbnNhY3Rpb24/IEkgZGlkIGxvb2sgdGhpcyB3YXksIGJ1dCBhY3R1YWxseSBqdXN0IGZld2VyIGRhdGEgcG9pbnRzIGluIHRoZXNlIGNhdGVnb3JpZXMuCiAKYGBge3J9CmNsYXJrcyAlPiUKbXV0YXRlKGFnZS5ncm91cD1jdXQoYWdlLCBicmVha3M9YygwLCAyMCwgMzAsIDQwLCA1MCwgMTAwKSwgbGFiZWxzPWMoIjwyMCIsIjIwcyIsIjMwcyIsIjQwcyIsIjUwcyIpKSkgJT4lCiAgZ2dwbG90KCBhZXMoeCA9IGFnZS5ncm91cCwgeSA9IHRvdGFsLnNwZW5kLnBlci50cmFucywgZ3JvdXAgPSBhZ2UuZ3JvdXAsIGNvbG91ciA9IG9ubGluZS5wcm9maWxlKSkgKwogICAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGFscGhhID0gMSkpKSsKICAgIGdlb21faml0dGVyKGFscGhhID0gMC4xICkgKwogICAgZ2VvbV9ib3hwbG90KGFscGhhID0gMCkgIApgYGAKIAogIEFyZSBvbGRlciBwZW9wbGUgc3BlbmRpbmcgbW9yZSBtb25leT8KIApgYGB7cn0KY2xhcmtzICU+JQptdXRhdGUoYWdlLmdyb3VwPWN1dChhZ2UsIGJyZWFrcz1jKDAsIDIwLCAzMCwgNDAsIDUwLCAxMDApLCBsYWJlbHM9YygiPDIwIiwiMjBzIiwiMzBzIiwiNDBzIiwiNTBzIikpKSAlPiUKICBnZ3Bsb3QoIGFlcyh4ID0gYWdlLmdyb3VwLCB5ID0gdG90YWwuc3BlbmQsIGdyb3VwID0gYWdlLmdyb3VwLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkrCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSApICsKICAgIGdlb21fYm94cGxvdChhbHBoYSA9IDApICAKYGBgCiAKICBBcmUgb2xkZXIgcGVvcGxlIHNwZW5kaW5nIG1vcmUgZnJlcXVlbnRseT8KIApgYGB7cn0KY2xhcmtzLnNwZW5kICU+JQptdXRhdGUoYWdlLmdyb3VwPWN1dChhZ2UsIGJyZWFrcz1jKDAsIDIwLCAzMCwgNDAsIDUwLCAxMDApLCBsYWJlbHM9YygiPDIwIiwiMjBzIiwiMzBzIiwiNDBzIiwiNTBzIikpKSAlPiUKICBnZ3Bsb3QoIGFlcyh4ID0gYWdlLmdyb3VwLCB5ID0gdG90YWwudHJhbnMsIGdyb3VwID0gYWdlLmdyb3VwLCBjb2xvdXIgPSBvbmxpbmUucHJvZmlsZSkpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChhbHBoYSA9IDEpKSkrCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSApICsKICAgIGdlb21fYm94cGxvdChhbHBoYSA9IDApICAKYGBgCgoKICMgU2F0aXNmYWN0aW9uIGFzIGEgaW5wdXQ/IAogCmBgYHtyfQpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2Uobm9fcm93cyA9IGxlbmd0aChjdXN0LmlkKSkKCmNsYXJrcyAlPiUgCiAgZ3JvdXBfYnkoc2F0LnNlcnZpY2UpICU+JQogIHN1bW1hcmlzZShtZWFuX3NwZW5kID0gbWVhbih0b3RhbC5zcGVuZCkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9hZ2UgPSBtZWFuKGFnZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVhbl9jcmVkaXQgPSBtZWFuKGNyZWRpdC5zY29yZSkpCgpjbGFya3MgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVkaWFuLmRpc3RhbmNlID0gbWVkaWFuKGRpc3RhbmNlLnRvLnN0b3JlKSkKCmZpbHRlcihjbGFya3MsIHNhdC5zZWxlY3Rpb24gIT0gIk5BIikgJT4lIAogIGdyb3VwX2J5KHNhdC5zZXJ2aWNlKSAlPiUKICBzdW1tYXJpc2UobWVhbi5zYXQuc2VsID0gbWVhbihzYXQuc2VsZWN0aW9uKSkKYGBgCgpUaGVyZSBkb2Vzbid0IGFwcGVhciB0byBiZSBhbnkgY29ycmVsYXRpb24gYmV0d2VlbiBzYXRpc2ZhY3Rpb24gb3Igc2VydmljZSBhbmQgYW55IG90aGVyIHZhcmlhYmxlcywgZXhjZXB0IGZvciBzYXRpc2ZhY3Rpb24gb2Ygc2VsZWN0aW9uIHdoaWNoIHlvdSB3b3VsZCBleHBlY3QgaGlnaCBzY29yZXJzIHdvdWxkIHNjb3JlIGhpZ2ggb24gYm90aAogCiAKIEFyZSBtb3JlIHNhdGlzZmllZCBwZW9wbGUgbWFraW5nIG1vcmUgcHVyY2hhc2VzPwpgYGB7cn0KCmZpbHRlcihjbGFya3MsIHNhdC5zZXJ2aWNlICE9ICJOQSIpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gc2F0LnNlcnZpY2UsIHkgPSB0b3RhbC50cmFucywgZ3JvdXAgPSBzYXQuc2VydmljZSwgY29sb3VyID0gb25saW5lLnByb2ZpbGUpKSArCiAgICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxKSkpKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjEgKSArCiAgICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwKSAgCmBgYAogCiAKIAogICBBcmUgbW9yZSBzYXRpc2ZpZWQgcGVvcGxlIHNwZW5kaW5nIG1vcmU/CmBgYHtyfQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlcnZpY2UgIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VydmljZSwgeSA9IHRvdGFsLnNwZW5kLCBncm91cCA9IHNhdC5zZXJ2aWNlLCBjb2xvciA9IHByb3Auc3BlbmQub25saW5lKSkgKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjEgKSArCiAgICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwKSAgCmBgYAoKCmBgYHtyfQpmaWx0ZXIoY2xhcmtzLCBzYXQuc2VsZWN0aW9uICE9ICJOQSIpICU+JQpnZ3Bsb3QoIGFlcyh4ID0gc2F0LnNlbGVjdGlvbiwgeSA9IHRvdGFsLnNwZW5kLCBncm91cCA9IHNhdC5zZWxlY3Rpb24sIGNvbG9yID0gcHJvcC5zcGVuZC5vbmxpbmUpKSArCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSApICsKICAgIGdlb21fYm94cGxvdChhbHBoYSA9IDApICAKYGBgCgoKCmBgYHtyfQpmaWx0ZXIoY2xhcmtzLCBzYXQuc2VydmljZSAhPSAiTkEiKSAlPiUKZmlsdGVyKCAhaXMubmFuKHByb3Auc3BlbmQub25saW5lKSkgJT4lCmdncGxvdCggYWVzKHggPSBwcm9wLnNwZW5kLm9ubGluZSwgeSA9IHNhdC5zZXJ2aWNlKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSApIAoKYGBgCmBgYHtyfQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlcnZpY2UgIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VydmljZSwgeSA9ICBwcm9wLnNwZW5kLm9ubGluZSwgZ3JvdXAgPSBzYXQuc2VydmljZSkpICsKICAgIGdlb21faml0dGVyKGFscGhhID0gMC4wNSApICsKICAgIGdlb21fdmlvbGluKGFscGhhID0gMCwgY29sb3VyID0gInJlZCIpICAKYGBgCgpgYGB7cn0KCmZpbHRlcihjbGFya3MsIHNhdC5zZWxlY3Rpb24gIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VsZWN0aW9uLCB5ID0gIHByb3Auc3BlbmQub25saW5lLCBncm91cCA9IHNhdC5zZWxlY3Rpb24pKSArCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMDUgKSArCiAgICBnZW9tX3Zpb2xpbihhbHBoYSA9IDAsIGNvbG91ciA9ICJyZWQiKSAgCmBgYAoKCgpgYGB7cn0KY2xhcmtzJT4lCmdyb3VwX2J5KHNhdC5zZWxlY3Rpb24pJT4lCnN1bW1hcml6ZShtZWFuLnNwZW5kID0gbWVhbih0b3RhbC5zcGVuZCkpJT4lCiAgZ2dwbG90KCBhZXMoeD1zYXQuc2VsZWN0aW9uLCB5PW1lYW4uc3BlbmQpKSArIAogIGdlb21fY29sKCkKYGBgCgoKYGBge3J9CmNsYXJrcyU+JQpncm91cF9ieShzYXQuc2VydmljZSklPiUKc3VtbWFyaXplKG1lYW4uc3BlbmQgPSBtZWFuKHRvdGFsLnNwZW5kKSklPiUKICBnZ3Bsb3QoIGFlcyh4PXNhdC5zZXJ2aWNlLCB5PW1lYW4uc3BlbmQpKSArIAogIGdlb21fY29sKCkKYGBgCgoKCmBgYHtyfQoKZmlsdGVyKGNsYXJrcywgc2F0LnNlbGVjdGlvbiAhPSAiTkEiKSAlPiUKZ2dwbG90KCBhZXMoeCA9IHNhdC5zZWxlY3Rpb24pKSArCiAgICBnZW9tX2JhciggKSAKICAKYGBgCgoKYGBge3J9CmZpbHRlcihjbGFya3MsIHNhdC5zZWxlY3Rpb24gIT0gIk5BIikgJT4lCmdncGxvdCggYWVzKHggPSBzYXQuc2VydmljZSkpICsKICAgIGdlb21fYmFyKCApIAogIApgYGAKCmBgYHtyfQpjbGFya3MlPiUKbXV0YXRlKG5vLm9ubGluZS52aXNpdHMgPSBvbmxpbmUudmlzaXRzPT0wKSAlPiUgCmdncGxvdCggYWVzKHggPSBuby5vbmxpbmUudmlzaXRzLCB5ID0gdG90YWwuc3BlbmQsIGdyb3VwID0gbm8ub25saW5lLnZpc2l0cywgY29sb3VyID0gc2F0LnNlcnZpY2UpKSArCiAgICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMDUgKSArCiAgICBnZW9tX3Zpb2xpbihhbHBoYSA9IDAsIGNvbG91ciA9ICJyZWQiKSAgCmBgYAoKYGBge3J9CmNsYXJrcy5zcGVuZCU+JQpmaWx0ZXIob25saW5lLnByb2ZpbGUgIT0gIk5vbmUiKSAlPiUKZ2dwbG90KCBhZXMoeCA9IG9ubGluZS5wcm9maWxlLCB5ID0gdG90YWwuc3BlbmQsIGdyb3VwID0gb25saW5lLnByb2ZpbGUsIGNvbG91ciA9IHNhdC5zZXJ2aWNlKSkgKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjA1ICkgKwogICAgZ2VvbV92aW9saW4oYWxwaGEgPSAwLCBjb2xvdXIgPSAicmVkIikgIApgYGAKCgoKYGBge3J9CmNsYXJrcy5zcGVuZCU+JQpmaWx0ZXIob25saW5lLnByb2ZpbGUgIT0gIk5vbmUiKSAlPiUKZ2dwbG90KCBhZXMoeCA9IG9ubGluZS5wcm9maWxlLCB5ID0gZGlzdGFuY2UudG8uc3RvcmUsIGdyb3VwID0gb25saW5lLnByb2ZpbGUsIGNvbG91ciA9IHNhdC5zZXJ2aWNlKSkgKwogICAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjA1ICkgKwogICAgZ2VvbV92aW9saW4oYWxwaGEgPSAwLCBjb2xvdXIgPSAicmVkIikgIApgYGAKCgoKCmBgYHtyfQpjbGFya3MlPiUKZmlsdGVyKHN0b3JlLnNwZW5kPjApJT4lCmdncGxvdCggYWVzKHggPSBkaXN0YW5jZS50by5zdG9yZSwgeSA9IHN0b3JlLnNwZW5kLCBjb2xvdXIgPSBzYXQuc2VydmljZSkpICsKZ2VvbV9wb2ludChhbHBoYT0wLjEpCmBgYAoKCgpgYGB7cn0KY2xhcmtzJT4lCm11dGF0ZShzdG9yZSA9IHN0b3JlLnNwZW5kID4gMCklPiUKZ3JvdXBfYnkoc3RvcmUpICU+JQpzdW1tYXJpemUobWVhbl9zcGVuZCA9IG1lYW4ob25saW5lLnNwZW5kKSkKCmBgYAoKYGBge3J9CmNsYXJrcyU+JQptdXRhdGUoc3RvcmUgPSBzdG9yZS5zcGVuZCA+IDApJT4lCmdncGxvdChhZXMoeD1zdG9yZSwgeT10b3RhbC5zcGVuZCAsIGdyb3VwPXN0b3JlKSkrCiAgZ2VvbV92aW9saW4oKQoKYGBgCgoKCgo=